using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Data.SqlClient;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml;

namespace CtxReader {
	/// <summary>
	/// handles device-specific context (default is to use memory-based context)
	/// </summary>
	public class Context {
		[Serializable]
		public class ContextProperties {
			public string _stateName="";
			public string _application=String.Empty;
			public Hashtable _values=new Hashtable();
		}

		protected string _address;
		protected string _bonus=string.Empty;
		protected string _warehouse=string.Empty;
		protected string _previousState=string.Empty;
		protected string _previousApplication=string.Empty;
		protected ContextProperties _properties=new ContextProperties();
		protected string _connectionString;
		protected const string INJECT_MESSAGE_STRING="INJECTEDMESSAGE";
		//protected string _handsetWarehouseCode = String.Empty;

		/// <summary>
		/// default constructor
		/// </summary>
		private Context() {
		}
		/// <summary>
		/// custom constructor
		/// </summary>
		[Obsolete]
		public Context(string address,string connectionString,string handsetWarehouseCode) {
			_address=address;
			_connectionString=connectionString;
			//_handsetWarehouseCode = handsetWarehouseCode;
		}
		public Context(string address,string connectionString) {
			_address=address;
			_connectionString=connectionString;

		}

		/// <summary>
		/// get address
		/// </summary>
		public string Address {
			get { return _address; }
		}

		public Hashtable Properties {
			get { return _properties._values; }
			set { _properties._values=value; }
		}


		/// <summary>
		/// get state name
		/// </summary>
		public string StateName {
			get { return _properties._stateName; }
			set { _properties._stateName=value; }
		}

		internal string Application {
			get { return _properties._application; }
			set { _properties._application=value; }
		}

		internal string PreviousApplication {
			get { return _previousApplication; }
			set { _previousApplication=value; }
		}

		internal string PreviousStateName {
			get { return _previousState; }
			set { _previousState=value; }
		}

		internal string Bonus {
			get { return _bonus; }
		}

		internal string Warehouse {
			get { return _warehouse; }
		}

		internal void ClearExtraInfo() {
			_bonus=string.Empty;
			_warehouse=string.Empty;
		}

		internal void SetBonus(string BonusIdent) {
			object localObj=GetProperty(BonusIdent);
			if(localObj!=null&&localObj.GetType()==typeof(string))
			{
				_bonus=(string)localObj;
			}
			else
			{
				_bonus=string.Empty;
			}
		}

		internal void SetWarehouse(string warehouseIdent,string defaultWarehouse) {
			object localObj=GetProperty(warehouseIdent);
			if(localObj!=null&&localObj.GetType()==typeof(string))
			{
				_warehouse=(string)localObj;
			}
			else
			{
				_warehouse=defaultWarehouse;
			}
		}

		/// <summary>
		/// Gets/Sets the message to show to the user, if available
		/// </summary>
		public string InjectedMessage {
			get { return GetPropertyString(INJECT_MESSAGE_STRING,String.Empty); }
			set { SetProperty(INJECT_MESSAGE_STRING,value); }
		}

		/// <summary>
		/// Loops through all propeties contained within and removes them
		/// </summary>
		public void ClearProperties() {
			Hashtable copyOfProperties=(Hashtable)Properties.Clone();
			foreach(DictionaryEntry entry in copyOfProperties)
			{
				string keyName=Convert.ToString(entry.Key);
				if((keyName!="output")&&(keyName!="Next.Systems.RF.RootMenu.ApplicationName")&&(keyName!="lastSequence")&&(keyName!="lastFrame"))
					Properties.Remove(entry.Key);
			}
		}

		/// <summary>
		/// lock the context
		/// </summary>
		internal bool Lock() {
			bool result=false;
			using(SqlConnection connection=new SqlConnection(_connectionString))
			{
				connection.Open();
				string sql="rfLockState";
				using(SqlCommand command=new SqlCommand(sql,connection))
				{
					command.CommandTimeout=30;
					command.CommandType=CommandType.StoredProcedure;
					SqlParameter param0=new SqlParameter("@address",SqlDbType.VarChar,15);
					param0.Value=_address;
					command.Parameters.Add(param0);
					try
					{
						object commandResult=command.ExecuteScalar();
						if(commandResult!=null)
						{
							int recordCount=(int)commandResult;
							result=(recordCount==1);
						}
						else
						{
							// did not acquire lock so we need to do something
							// just return failure for now
							result=false;
						}
					}
					catch(Exception ex)
					{
						EventLog.WriteEntry("RfService","Exception occured attempting to Lock Context "+ex.ToString());
						try
						{
							command.CommandText="rfUnlockState";
							command.Parameters.Clear();
							command.Parameters.Add(param0);
							command.ExecuteNonQuery();
						}
						catch(Exception exception)
						{
							Console.WriteLine("exception unlocking state - {0}, unlocking...",exception.ToString());
						}
						// an exception will return failure
						result=false;
					}
				}
				connection.Close();
			}
			return result;
		}

		/// <summary>
		/// get the context state
		/// </summary>
		public bool GetState(bool readOnly) {
			bool result=false;
			using(SqlConnection connection=new SqlConnection(_connectionString))
			{
				connection.Open();
				string sql="rfGetState";
				using(SqlCommand command=new SqlCommand(sql,connection))
				{
					command.CommandTimeout=10;
					command.CommandType=CommandType.StoredProcedure;
					SqlParameter param0=new SqlParameter("@address",SqlDbType.VarChar,15);
					param0.Value=_address;
					command.Parameters.Add(param0);
					try
					{
						object commandResult=command.ExecuteScalar();
						if(commandResult is System.Byte[])
						{
							try
							{
								byte[] stateBytes=(byte[])commandResult;
								// save data into memory stream and deserialize state
								MemoryStream ms=new MemoryStream();
								BinaryFormatter bf=new BinaryFormatter();
								ms.Write(stateBytes,0,stateBytes.Length);
								ms.Seek(0,0);
								//bf.Binder=new DeserializationBinder();
								_properties=(ContextProperties)bf.Deserialize(ms);
								ms.Close();
								// we got the lock on the record
								result=true;
							}
							catch(Exception exception)
							{
								EventLog.WriteEntry("Rf","deserialize exception in Context.GetState() "+exception.ToString());
								Console.WriteLine("exception deserializing in GetState - {0}",exception.ToString());
								if(!readOnly)
								{
									command.CommandText="rfResetState";
									command.Parameters.Clear();
									command.Parameters.Add(param0);
									command.ExecuteNonQuery();
								}
							}
						}
						else
						{
							// state has been nullified so reset state and return
							_properties=new ContextProperties();
							Console.WriteLine("state reset for device at {0}",_address);
							result=true;
						}
					}
					catch(Exception e)
					{
						//SystemParameters.LogWarning("exception in Context.GetState() : {0}",e.ToString());
						Console.WriteLine("exception - {0}, releasing context...",e.ToString());
						if(!readOnly)
						{
							command.CommandText="rfResetState";
							command.Parameters.Clear();
							command.Parameters.Add(param0);
							command.ExecuteNonQuery();
						}
						// an exception will return failure
						result=false;
					}
				}
				connection.Close();
			}
			return result;
		}
		public bool GetHistoricalState(DateTime inserted,bool readOnly) {
			bool result=false;
			using(SqlConnection connection=new SqlConnection(_connectionString))
			{
				connection.Open();
				string sql="rfGetStateHistorical";
				using(SqlCommand command=new SqlCommand(sql,connection))
				{
					command.CommandTimeout=10;
					command.CommandType=CommandType.StoredProcedure;
					SqlParameter param0=new SqlParameter("@address",SqlDbType.VarChar,15);
					param0.Value=_address;
					command.Parameters.Add(param0);
					SqlParameter param1=new SqlParameter("@inserted",SqlDbType.DateTime);
					param1.Value=inserted;
					command.Parameters.Add(param1);
					try
					{
						object commandResult=command.ExecuteScalar();
						if(commandResult is System.Byte[])
						{
							try
							{
								byte[] stateBytes=(byte[])commandResult;
								// save data into memory stream and deserialize state
								MemoryStream ms=new MemoryStream();
								BinaryFormatter bf=new BinaryFormatter();
								ms.Write(stateBytes,0,stateBytes.Length);
								ms.Seek(0,0);
								//bf.Binder=new DeserializationBinder();
								_properties=(ContextProperties)bf.Deserialize(ms);
								ms.Close();
								// we got the lock on the record
								result=true;
							}
							catch(Exception exception)
							{
								//SystemParameters.LogWarning("deserialize exception in Context.GetState() : {0}",exception.ToString());
								Console.WriteLine("exception deserializing in GetState - {0}",exception.ToString());
								if(!readOnly)
								{
									command.CommandText="rfResetState";
									command.Parameters.Clear();
									command.Parameters.Add(param0);
									command.ExecuteNonQuery();
								}
							}
						}
						else
						{
							// state has been nullified so reset state and return
							_properties=new ContextProperties();
							Console.WriteLine("state reset for device at {0}",_address);
							result=true;
						}
					}
					catch(Exception e)
					{
						//SystemParameters.LogWarning("exception in Context.GetState() : {0}",e.ToString());
						Console.WriteLine("exception - {0}, releasing context...",e.ToString());
						if(!readOnly)
						{
							command.CommandText="rfResetState";
							command.Parameters.Clear();
							command.Parameters.Add(param0);
							command.ExecuteNonQuery();
						}
						// an exception will return failure
						result=false;
					}
				}
				connection.Close();
			}
			return result;
		}
		/// <summary>
		/// release the lock on the context
		/// </summary>
		internal bool Release() {
			return Release(true);
		}

		internal bool Release(bool StateHasChanged) {
			using(SqlConnection connection=new SqlConnection(_connectionString))
			{
				connection.Open();
				string sql="rfUpdateStateWithBonus";
				using(SqlCommand command=new SqlCommand(sql,connection))
				{
					command.CommandType=CommandType.StoredProcedure;
					try
					{
						// serialize state into memory stream
						MemoryStream ms=new MemoryStream();
						BinaryFormatter bf=new BinaryFormatter();
						bf.Serialize(ms,_properties);
						ms.Seek(0,0);
						byte[] stateData=ms.ToArray();
						// save object to database
						SqlParameter param0=new SqlParameter("@address",SqlDbType.Char,15);
						param0.Value=_address;
						command.Parameters.Add(param0);
						SqlParameter param1=new SqlParameter("@state",SqlDbType.Image,stateData.Length);
						param1.Value=stateData;
						command.Parameters.Add(param1);
						SqlParameter param2=new SqlParameter("@fromApplication",SqlDbType.VarChar,50);
						param2.Value=PreviousApplication;
						command.Parameters.Add(param2);
						SqlParameter param3=new SqlParameter("@toApplication",SqlDbType.VarChar,50);
						param3.Value=Application;
						command.Parameters.Add(param3);
						SqlParameter param4=new SqlParameter("@bonus",SqlDbType.VarChar,10);
						param4.Value=Bonus;
						command.Parameters.Add(param4);
						SqlParameter param5=new SqlParameter("@warehouseCode",SqlDbType.VarChar,2);
						param5.Value=Warehouse;
						command.Parameters.Add(param5);
						SqlParameter param6=new SqlParameter("@fromState",SqlDbType.VarChar,50);
						param6.Value=PreviousStateName;
						command.Parameters.Add(param6);
						SqlParameter param7=new SqlParameter("@currentState",SqlDbType.VarChar,50);
						param7.Value=StateName;
						command.Parameters.Add(param7);
						//SqlParameter param8 = new SqlParameter("@StateHasChanged", SqlDbType.Bit);
						//param8.Value = StateHasChanged;
						//command.Parameters.Add(param8);
						command.ExecuteNonQuery();
						// close memory stream
						ms.Close();
					}
					catch(Exception exception)
					{
						Console.WriteLine("exception serializing in Release - {0}",exception.ToString());
						command.CommandText="rfResetState";
						command.Parameters.Clear();
						SqlParameter param0=new SqlParameter("@address",SqlDbType.Char,15);
						param0.Value=_address;
						command.Parameters.Add(param0);
						command.ExecuteNonQuery();
					}
				}
				connection.Close();
			}
			return true;
		}
		/// <summary>
		/// add a property
		/// </summary>
		public void SetProperty(string name,object propertyValue) {
			if(_properties._values.ContainsKey(name))
			{
				_properties._values[name]=propertyValue;
			}
			else
			{
				_properties._values.Add(name,propertyValue);
			}
		}
		/// <summary>
		/// delete a property
		/// </summary>
		public void DeleteProperty(string name) {
			if(_properties._values.ContainsKey(name))
			{
				_properties._values.Remove(name);
			}
		}
		/// <summary>
		/// get a property value
		/// </summary>
		public object GetProperty(string name) {
			object result=null;
			if(_properties._values.ContainsKey(name))
			{
				result=_properties._values[name];
			}
			return result;
		}
		/// <summary>
		/// get a property value as a string
		/// </summary>
		public string GetPropertyString(string name,string defaultValue) {
			string result=defaultValue;
			if(_properties._values.ContainsKey(name))
			{
				result=_properties._values[name].ToString();
			}
			return result;
		}
		/// <summary>
		/// get a property value as an integer
		/// </summary>
		public int GetPropertyInteger(string name,int defaultValue) {
			int result=defaultValue;
			if(_properties._values.ContainsKey(name))
			{
				result=(int)_properties._values[name];
			}
			return result;
		}
		/// <summary>
		/// dump the context properties
		/// </summary>
		private void DumpProperties() {
			Console.WriteLine("Context dump for device at address {0}:\n",_address);
			Console.WriteLine("  State={0}",_properties._stateName);
			foreach(DictionaryEntry entry in _properties._values)
			{
				////if(entry.Value is OutputFrame)
				//{
				//  //OutputFrame frame=entry.Value as OutputFrame;
				//  Console.WriteLine("\n** Frame **\n");
				//  frame.DiagnosticDumpToConsole();
				//  Console.WriteLine("\n***********\n");
				//}
				//else
				{
					if(entry.Value==null)
					{
						Console.WriteLine("  {0}=NULL",entry.Key.ToString());
					}
					else
					{
						Console.WriteLine("  {0}={1}",entry.Key.ToString(),ConvertToString(entry.Value));
					}
				}
			}
			Console.WriteLine("\ndone.");
		}

		private string ConvertToString(object rfValue) {
			string rfValueStr=rfValue.ToString();
			if(rfValue.GetType()==typeof(List<string>))
			{
				List<string> values=(List<string>)rfValue;
				if(values.Count>0)
				{
					rfValueStr=String.Empty;
					foreach(string s in values)
						rfValueStr=String.Concat(rfValueStr,s,", ");
					rfValueStr=rfValueStr.Substring(0,rfValueStr.Length-2);
				}
				else
					rfValueStr=String.Concat(rfValueStr," - Empty!");
			}
			else if(rfValue.GetType()==typeof(string[]))
			{
				string[] values=(string[])rfValue;
				if(values.Length>0)
				{
					rfValueStr=String.Empty;
					foreach(string s in values)
						rfValueStr=String.Concat(rfValueStr,s,", ");
					rfValueStr=rfValueStr.Substring(0,rfValueStr.Length-2);
				}
				else
					rfValueStr=String.Concat(rfValueStr," - Empty!");
			}

			return rfValueStr;
		}

		/// <summary>
		/// dump the context to the console, and return
		/// </summary>
		public static string Dump(string connectionString,string address) {
			Context context=new Context();
			context._address=address;
			context._connectionString=connectionString;
			context.GetState(true);
			if(context.StateName=="")
			{
				string noState="<NO STATE>";
				Console.WriteLine(noState);
				return noState;
			}
			else
			{
				context.DumpProperties();
				return context.DumpPropertiesToXML();
			}
		}
		/// <summary>
		/// dump the context to the console, and return
		/// </summary>
		public static string DumpHistorical(string connectionString,string address,DateTime inserted) {
			Context context=new Context();
			context._address=address;
			context._connectionString=connectionString;
			context.GetHistoricalState(inserted,true);
			if(context.StateName=="")
			{
				string noState="<NO STATE>";
				Console.WriteLine(noState);
				return noState;
			}
			else
			{
				context.DumpProperties();
				return context.DumpPropertiesToXML();
			}
		}
		/// <summary>
		/// formats the context to an xml file
		/// </summary>
		/// <returns></returns>
		private string DumpPropertiesToXML() {
			// to check size of objects being written out...
			MemoryStream stream=new MemoryStream();
			BinaryFormatter formatter=new BinaryFormatter();

			StringBuilder sb=new StringBuilder();
			XmlWriterSettings settings=new XmlWriterSettings();
			settings.Indent=true;
			settings.IndentChars=("    ");
			settings.ConformanceLevel=ConformanceLevel.Auto;
			using(XmlWriter writer=XmlWriter.Create(sb,settings))
			{
				writer.WriteStartElement("context");
				writer.WriteElementString("address",_address);
				writer.WriteElementString("state",_properties._stateName);
				writer.WriteStartElement("frames");
				foreach(DictionaryEntry entry in _properties._values)
				{
					//if(entry.Value is OutputFrame)
						//writer.WriteElementString("frame",XMLChecker.RemoveIllegalCharacters((entry.Value as OutputFrame).DiagnosticDumpToConsole()));
				}
				writer.WriteEndElement();
				writer.WriteStartElement("properties");
				foreach(DictionaryEntry entry in _properties._values)
				{
					try
					{
						//if(!(entry.Value is OutputFrame))
						{
							writer.WriteStartElement("property");
							writer.WriteAttributeString("name",entry.Key.ToString());

							formatter.Serialize(stream,entry.Value);
							writer.WriteAttributeString("size",String.Format("{0:0.00}",Convert.ToDecimal(stream.Length/1048576m)));
							stream=new MemoryStream();

							writer.WriteString(entry.Value==null?String.Empty:stripNonValidXMLCharacters(ConvertToString(entry.Value)));
							writer.WriteEndElement();
						}
					}
					catch(Exception ex)
					{
						// something might go wrong when doing tostring on an object, so just ignore it and move on showing the name of it for ref
						writer.WriteString(string.Format("Could not get value. Error was {0}",ex.Message));
						writer.WriteEndElement();
					}
				}
				writer.WriteEndElement();
				writer.WriteEndElement();
				writer.Flush();
				writer.Close();
			}
			return sb.ToString();
		}

		public static string stripNonValidXMLCharacters(string s) {
			StringBuilder _validXML=new StringBuilder(s.Length,s.Length);
			char current;
			char[] charArray=s.ToCharArray();

			if(string.IsNullOrEmpty(s))
				return string.Empty;

			for(int i=0;i<charArray.Length;i++)
			{
				current=charArray[i];
				if((current==0x9)||
					(current==0xA)||
					(current==0xD)||
					((current>=0x20)&&(current<=0xD7FF))||
					((current>=0xE000)&&(current<=0xFFFD))||
					((current>=0x10000)&&(current<=0x10FFFF)))
					_validXML.Append(current);
			}
			return _validXML.ToString();
		}

		internal static void InjectMessage(string address,string connectionString,string message) {
			Context c=new Context(address,connectionString,String.Empty);
			c.Lock();
			c.GetState(false);
			c.InjectedMessage=message;
			c.Release();
		}
	}
}
